import '@/assets/js/aes-decryptor'
import { downloadUrl, downloadUrlByAjax } from '@/api/spider/downloader'
import { AESDecryptor } from '@/assets/js/aes-decryptor'

export class DownloaderUtil {
  constructor(downloadInfo, vue) {
    this.id = downloadInfo.id || crypto.randomUUID()
    this.isSuccess = downloadInfo.isSuccess || false
    this.$message = vue.$message
    this.baseUrl = downloadInfo.baseUrl
    this.url = downloadInfo.url
    this.fileName = downloadInfo.fileName
    this.percentage = parseFloat(downloadInfo.percentage) || 0
    this.isPause = downloadInfo.isPause || true
    this.durationSecond = parseFloat(downloadInfo.durationSecond) || 0 // 视频持续时长
    this.errorNum = 0 // 错误数
    this.finishNum = 0 // 已下载数
    this.downloadIndex = 0 // 当前下载片段
    this.finishList = [] // 下载完成项目
    this.tsUrlList = downloadInfo.tsUrlList || [] // ts URL数组
    this.m3u8Line = downloadInfo.m3u8Line || {}
    this.mediaFileList = downloadInfo.mediaFileList || [] // 下载的媒体数组
    this.rangeDownload = downloadInfo.rangeDownload || { // 特定范围下载
      isShowRange: false, // 是否显示范围下载
      startSegment: '', // 起始片段
      endSegment: '', // 截止片段
      targetSegment: 1 // 待下载片段
    }
    this.aesConf = downloadInfo.aesConf || { // AES 视频解密配置
      method: '', // 加密算法
      uri: '', // key 所在文件路径
      iv: '', // 偏移值
      key: '', // 秘钥
      decryptor: null
    }
  }
  textEncoderEncode(str) {
    return new TextEncoder().encode(str)
  }

  removeDownload() {
    this.$message = undefined
    this.isPause = true
    this.durationSecond = 0 // 视频持续时长
    this.errorNum = 0 // 错误数
    this.downloadIndex = 0 // 当前下载片段
    this.finishList = [] // 下载完成项目
    this.tsUrlList = [] // ts URL数组
    this.m3u8Line = {}
    this.mediaFileList = [] // 下载的媒体数组
    this.aesConf = {}
  }

  // 合成URL
  applyURL(targetURL, baseURL) {
    baseURL = baseURL || location.href
    if (targetURL.indexOf('http') === 0) {
      return targetURL
    } else if (targetURL[0] === '/') {
      const domain = baseURL.split('/')
      return domain[0] + '//' + domain[2] + targetURL
    } else {
      const domain = baseURL.split('/')
      domain.pop()
      return domain.join('/') + '/' + targetURL
    }
  }

  padPrefix(prefix, str) {
    const same = prefix.split('').reduce((acc, o) => !str.startsWith(acc += o) ? acc = '' : acc, '')
    if (same === '') {
      return prefix.concat(str)
    } else {
      return prefix.slice(0, -same.length).concat(str)
    }
  }

  downloadMp4(callback) {
    console.log(this.url)
    console.log(this.baseUrl)
    this.getM3U8(this.url, this.baseUrl, callback)
  }

  /**
   * 获取在线文件
   * @param targetUrl targetUrl
   * @param baseUrl baseUrl
   */
  getM3U8(targetUrl, baseUrl, callback) {
    if (!targetUrl) {
      this.$message.error('请输入链接')
      return
    } else if (targetUrl.toLowerCase().indexOf('m3u8') === -1) {
      this.$message.error('链接有误，请重新输入')
      return
    }
    downloadUrl(targetUrl).then((m3u8Str) => {
      this.tsUrlList = []
      this.finishList = []
      let rtn = false
      let start = false
      let isM3u8List = false
      // 提取 ts 视频片段地址
      m3u8Str.split('\n').forEach((item) => {
        if (item.toLowerCase().indexOf('.ts') > -1 || item.toLowerCase().indexOf('.image') > -1) {
          this.tsUrlList.push(this.applyURL(item, baseUrl))
          this.finishList.push({
            title: item,
            status: ''
          })
        } else if (item.toUpperCase().indexOf('#EXT-X-STREAM-INF') > -1) { // 找到真实地址开始的标识
          start = true
        } else if (start && item.toLowerCase().indexOf('.m3u8') > -1) { // 找到真实地址
          let keyUrl = item
          if (keyUrl.startsWith('http')) {
            targetUrl = keyUrl
          } else {
            if (keyUrl.startsWith('/')) {
              keyUrl = keyUrl.substring(1)
            }
            targetUrl = this.padPrefix(targetUrl.substring(0, targetUrl.lastIndexOf('/') + 1), keyUrl)
            isM3u8List = true
          }
          rtn = true
        }
      })
      if (rtn) {
        this.getM3U8(targetUrl, isM3u8List ? targetUrl : baseUrl, callback)
        return
      }
      let startSegment = Math.max(this.rangeDownload.startSegment || 1, 1) // 最小为 1
      let endSegment = Math.max(this.rangeDownload.endSegment || this.tsUrlList.length, 1)
      startSegment = Math.min(startSegment, this.tsUrlList.length) // 最大为 this.tsUrlList.length
      endSegment = Math.min(endSegment, this.tsUrlList.length)
      this.rangeDownload.startSegment = Math.min(startSegment, endSegment)
      this.rangeDownload.endSegment = Math.max(startSegment, endSegment)
      this.rangeDownload.targetSegment = this.rangeDownload.endSegment - this.rangeDownload.startSegment + 1
      this.downloadIndex = this.rangeDownload.startSegment - 1
      this.isPause = false
      // 检测视频 AES 加密
      if (m3u8Str.indexOf('#EXT-X-KEY') > -1) {
        this.aesConf.method = (m3u8Str.match(/(.*METHOD=([^,\s]+))/) || ['', '', ''])[2]
        this.aesConf.uri = (m3u8Str.match(/(.*URI="([^"]+))"/) || ['', '', ''])[2]
        this.aesConf.iv = (m3u8Str.match(/(.*IV=([^,\s]+))/) || ['', '', ''])[2]
        this.aesConf.iv = this.aesConf.iv ? this.textEncoderEncode(this.aesConf.iv) : ''
        this.aesConf.uri = this.applyURL(this.aesConf.uri, baseUrl)
        this.getAES(callback)
      } else if (this.tsUrlList.length > 0) {
        // 如果视频没加密，则直接下载片段，否则先下载秘钥
        this.downloadTS(callback)
      } else {
        this.alertError('资源为空，请查看链接是否有效')
      }
    }).catch(() => {
      this.alertError('链接不正确，请查看链接是否有效')
    })
  }

  // 获取AES配置
  getAES(callback) {
    downloadUrlByAjax({
      type: 'file',
      url: this.aesConf.uri,
      success: (key) => {
        this.aesConf.key = key
        this.aesConf.decryptor = new AESDecryptor()
        this.aesConf.decryptor.constructor()
        this.aesConf.decryptor.expandKey(this.aesConf.key)
        this.downloadTS(callback)
      },
      fail: () => {
        this.alertError('AES 配置不正确')
      }
    })
  }

  // ts 片段的 AES 解码
  aesDecrypt(data, index) {
    const iv = this.aesConf.iv || new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, index])
    return this.aesConf.decryptor.decrypt(data, 0, iv.buffer || iv, true)
  }

  downloadTSWhenUnComplete(callback) {
    const isPause = this.isPause // 使用另一个变量来保持下载前的暂停状态，避免回调后没修改
    const index = this.downloadIndex
    this.downloadIndex++
    if (this.finishList[index] && this.finishList[index].status === '') {
      downloadUrlByAjax({
        url: this.tsUrlList[index],
        type: 'file',
        success: (file) => {
          if (file.byteLength < 300) { // TODO 文件太小一般是没法下载的错误文件
            this.mediaFileList[index - this.rangeDownload.startSegment + 1] = new Uint8Array(0)
            this.finishNum++
            this.finishList[index].status = 'finish'
            if (this.finishNum < this.rangeDownload.endSegment) {
              !isPause && this.downloadTSWhenUnComplete(callback)
            }
          } else {
            this.dealTS(file, index, callback)
          }
        },
        fail: () => {
          this.mediaFileList[index - this.rangeDownload.startSegment + 1] = new Uint8Array(0)
          this.errorNum++
          this.finishList[index].status = 'error'
          if (this.downloadIndex < this.rangeDownload.endSegment) {
            !isPause && this.downloadTSWhenUnComplete(callback)
          }
        }
      })
    } else if (this.downloadIndex < this.rangeDownload.endSegment) { // 跳过已经成功的片段
      !isPause && this.downloadTSWhenUnComplete(callback)
    }
  }

  // 下载分片
  downloadTS(callback) {
    // 建立多少个线程
    for (let i = 0; i < Math.min(10, this.rangeDownload.targetSegment - this.finishNum); i++) {
      this.downloadTSWhenUnComplete(callback)
    }
    this.retryAll(callback)
  }

  // 处理 ts 片段，AES 解密、mp4 转码
  dealTS(file, index, callback) {
    const data = this.aesConf.uri ? this.aesDecrypt(file, index) : file
    this.conversionMp4(data, index, (afterData) => { // mp4 转码
      this.mediaFileList[index - this.rangeDownload.startSegment + 1] = afterData // 判断文件是否需要解密
      this.finishList[index].status = 'finish'
      this.finishNum++
      this.updatePercentage(callback)
      if (this.finishNum === this.rangeDownload.targetSegment) {
        this.downloadFile(this.mediaFileList, this.fileName, callback)
        this.removeDownload()
        this.updatePercentage(callback)
      }
      if (this.downloadIndex < this.rangeDownload.endSegment && !this.isPause) {
        this.downloadTSWhenUnComplete(callback)
      }
    })
  }
  success() {
    this.isSuccess = true
  }
  // 转码为 mp4
  conversionMp4(data, index, callback) {
    callback(data)
  }

  // 暂停与恢复
  togglePause(callback) {
    this.isPause = !this.isPause
    !this.isPause && this.retryAll(callback)
  }
  pause() {
    this.isPause = true
  }
  // 重新下载某个片段
  retry(index) {
    if (this.finishList[index].status === 'error') {
      this.finishList[index].status = ''
      downloadUrlByAjax({
        url: this.tsUrlList[index],
        type: 'file',
        success: (file) => {
          this.errorNum--
          this.dealTS(file, index)
        },
        fail: () => {
          this.finishList[index].status = 'error'
        }
      })
    }
  }

  // 重新下载所有错误片段
  retryAll(callback) {
    this.finishList.forEach((item) => { // 重置所有片段状态
      if (item.status === 'error') {
        item.status = ''
      }
    })
    this.errorNum = 0
    this.downloadIndex = this.rangeDownload.startSegment - 1
    this.downloadTS(callback)
  }

  // 下载整合后的TS文件
  downloadFile(fileDataList, fileName, callback) {
    let fileBlob = null
    const a = document.createElement('a')
    // fileBlob = new Blob(fileDataList, { type: 'video/mp4' }) // 创建一个Blob对象，并设置文件的 MIME 类型
    // a.download = fileName + '.mp4'
    fileBlob = new Blob(fileDataList, { type: 'video/MP2T' }) // 创建一个Blob对象，并设置文件的 MIME 类型
    a.download = fileName + '.mp4'
    a.href = URL.createObjectURL(fileBlob)
    a.style.display = 'none'
    document.body.appendChild(a)
    this.success()
    a.click()
    a.remove()
  }

  // 强制下载现有片段
  forceDownload(callback) {
    if (this.mediaFileList.length) {
      this.downloadFile(this.mediaFileList, this.fileName, callback)
      this.pause()
      this.updatePercentage(callback)
    } else {
      this.$message.error('当前无已下载片段')
    }
  }

  updatePercentage(callback) {
    this.percentage = parseInt((this.finishNum / this.rangeDownload.targetSegment * 100).toFixed(2))
    callback(this)
  }

  // 发生错误，进行提示
  alertError(tips) {
    this.$message.error(tips)
    this.isPause = true
  }
}
